#! /usr/bin/env python
# -*- coding: utf8 -*-

import pygtk
pygtk.require('2.0')

import gtk
import gtk.gdk
import gtk.glade

import sys
import os.path
import threading

if sys.version_info[:2] == (2, 5):
    from xml.etree import ElementTree as et
else:
    from elementtree import ElementTree as et

from dtd_parser import dtd_parser
from algorithm import algorithm
from distribution import distribution
from transformation import transformation, root

def threaded(f):
    def wrapper(*args):
        t = threading.Thread(target=f, args=args)
        t.setDaemon(True)
        t.start()

    wrapper.__name__ = f.__name__
    wrapper.__dict__ = f.__dict__
    wrapper.__doc__  = f.__doc__

    return wrapper

def indent(elem, level=0):
    i = "\n" + level*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        for elem in elem:
            indent(elem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

class xXMLc(object):
    def __init__(self):
        self.__bpath = "/".join(os.path.abspath(sys.modules[__name__].__file__).split("/")[:-1])

        self.__load_interface()
        self.__init_lists()

        self.__dtd          = None
        self.__myelements   = []

        self.__is_tree_view = False
        self.__algorithms   = {}

        self.__current_base_algorithm    = ""
        self.__current_base_distribution = ""

        self.__conf_root                 = ""
        self.__current_element           = ""
        self.__configured_elements       = {}

        self.__transform = transformation()
        self.__root      = root()

        self.__init_distributions()

    def __init_distributions(self):
        node = et.parse("%s/distributions.xml" % (self.__bpath,)).getroot()

        self.__distributions              = {}
        self.__base_distributions         = {}
        self.__base_distributions_by_name = {}

        combo = gtk.ListStore(str)

        for i in node.findall("./distribution"):
            distro = distribution(i)

            self.__base_distributions[distro.pretty_name]  = distro
            self.__base_distributions_by_name[distro.name] = distro

        self.__widgets.get_widget("distribution_combobox").set_model(combo)
        [combo.append([x]) for x in self.__base_distributions]

    def __load_interface(self):
        self.__widgets = gtk.glade.XML("%s/xXMLc.glade" % (self.__bpath,))
        self.__widgets.signal_autoconnect(self)

        self.__hauptfenster = self.__widgets.get_widget("hauptfenster")
        self.__hauptfenster.show_all()

    # Initialization crap
    def __init_lists(self):
        self.__algorithm_list       = gtk.TreeStore(str)
        self.__distribution_list    = gtk.TreeStore(str)
        self.__element_list         = gtk.TreeStore(str)

        self.__algorithm_tree    = self.__widgets.get_widget("algorithm_list")
        self.__distribution_tree = self.__widgets.get_widget("distribution_list")
        self.__element_tree      = self.__widgets.get_widget("element_list")

        algorithms = gtk.TreeViewColumn("Algorithm",                gtk.CellRendererText(), text = 0)
        distros    = gtk.TreeViewColumn("Probability Distribution", gtk.CellRendererText(), text = 0)
        elements   = gtk.TreeViewColumn("Element",                  gtk.CellRendererText(), text = 0)

        for i in [algorithms, distros, elements]:
            i.set_resizable(False)
            i.set_sort_column_id(0)

        self.__algorithm_tree.set_model(self.__algorithm_list)
        self.__distribution_tree.set_model(self.__distribution_list)
        self.__element_tree.set_model(self.__element_list)

        self.__algorithm_tree.append_column(algorithms)
        self.__distribution_tree.append_column(distros)
        self.__element_tree.append_column(elements)

    def __restart(self):
        map(self.__myelements.remove, self.__myelements)

        self.__algorithm_list.clear()
        self.__distribution_list.clear()
        self.__element_list.clear()

    # gtk signal handlers:
    def hauptfenster_exit(self, *args, **kwargs):
        gtk.main_quit()

    ## MAIN MENU ##
    def new_file(self, menu):
        self.__type = menu.name.split("_")[1]
        self.__load_algorithms()

        self.__widgets.get_widget("algorithms_hbox").set_sensitive(True)
        self.__widgets.get_widget("distributions_hbox").set_sensitive(True)
        self.__widgets.get_widget("elements_hbox").set_sensitive(True)
        self.__widgets.get_widget("savemenu").set_sensitive(True)

    @threaded
    def save_file(self, menu):
        gtk.gdk.threads_enter()
        buttons = (
            gtk.STOCK_SAVE,   gtk.RESPONSE_ACCEPT,
            gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
        )

        file_dialog = gtk.FileChooserDialog("Save config file", self.__hauptfenster, gtk.FILE_CHOOSER_ACTION_SAVE,
                                             buttons)

        filtro = gtk.FileFilter()
        filtro.add_pattern("*.xml")
        file_dialog.set_filter(filtro)

        if file_dialog.run() == gtk.RESPONSE_ACCEPT:
            fname = file_dialog.get_filenames()[0]

            root    = et.Element(self.__type)
            header  = et.Comment("Generated with xXMLc")
            algos   = et.Element("algorithms")
            distros = et.Element("distributions")
            transforms = et.Element("transformations")

            root.append(header)
            root.append(distros)
            root.append(algos)
            root.append(transforms)

            [algos.append(x) for x in self.__algorithms.values() if x != None]
            [distros.append(x) for x in self.__distributions.values() if x != None]
            [transforms.append(x) for x in self.__configured_elements.values() if x != None]

            indent(root)
            et.ElementTree(root).write(fname)

        file_dialog.destroy()
        gtk.gdk.threads_leave()

    ## SIGNALS ON THE ALGORITHMS NOTEBOOK ##
    def add_algorithm(self, widget):
        entry = self.__widgets.get_widget("new_algorithm_name")
        text = entry.get_text().strip()

        if text not in self.__algorithms.keys() and text != "":
            self.__algorithms[text] = None
            self.__algorithm_list.append(None, [text])
            self.__transform.add_algorithm([text])

        entry.set_text("")

    def algorithm_apply(self, widget):
        self.__algorithms[self.__current_algorithm] = self.__base_algorithms[self.__current_base_algorithm].xml(self.__current_algorithm)
        self.__widgets.get_widget("algorithm_revert").set_sensitive(True)

    def algorithm_revert(self, widget):
        if self.__algorithms[self.__current_algorithm] != None:
            base = self.__algorithms[self.__current_algorithm].find("baseAlgorithm").text

            if self.__current_base_algorithm != self.__base_algo_by_name[base].pretty_name:
                self.__base_algorithms[self.__current_base_algorithm].remove_elements()
                self.__base_algo_by_name[base].append_elements(self.__widgets.get_widget("algorithm_vbox"))

            self.__base_algo_by_name[base].open_state(self.__algorithms[self.__current_algorithm])
            self.__current_base_algorithm = self.__base_algo_by_name[base].pretty_name

    def algorithm_combobox_changed(self, combo):
        if self.__current_base_algorithm != "":
            self.__base_algorithms[self.__current_base_algorithm].remove_elements()

        current = combo.get_model()[combo.get_active()][0]

        self.__current_base_algorithm = current
        self.__base_algorithms[current].append_elements(self.__widgets.get_widget("algorithm_vbox"))
        self.__widgets.get_widget("algorithm_apply").set_sensitive(True)

    def algorithm_list_press(self, widget, event):
        if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
            if widget.get_selection().get_selected()[1]:
                name = self.__algorithm_list.get_value(widget.get_selection().get_selected()[1], 0)

                self.__current_algorithm = name

                self.__widgets.get_widget("algorithm_name").set_text("<big><b>%s</b></big>" % (name,))
                self.__widgets.get_widget("algorithm_name").set_use_markup(True)
                self.__widgets.get_widget("algorithm_vbox").set_sensitive(True)

                self.__widgets.get_widget("algorithm_revert").set_sensitive(False)
                self.__widgets.get_widget("algorithm_combobox").set_active(-1)
                self.__widgets.get_widget("algorithm_apply").set_sensitive(False)

                if self.__algorithms[name]:
                    base = self.__algorithms[name].find("baseAlgorithm").text
                    pretty_base = self.__base_algo_by_name[base].pretty_name
                    modelo = self.__widgets.get_widget("algorithm_combobox").get_model()

                    for i in modelo:
                        if i[0] == pretty_base:
                            self.__widgets.get_widget("algorithm_combobox").set_active_iter(i.iter)
                            break

                    if self.__current_base_algorithm != pretty_base:
                        self.__base_algorithms[self.__current_base_algorithm].remove_elements()
                        self.__base_algo_by_name[base].append_elements(self.__widgets.get_widget("algorithm_vbox"))
                        self.__current_base_algorithm = pretty_base

                    self.__base_algorithms[pretty_base].open_state(self.__algorithms[name])
                    self.__widgets.get_widget("algorithm_apply").set_sensitive(True)
                elif self.__current_base_algorithm != "":
                    self.__base_algorithms[self.__current_base_algorithm].remove_elements()

    ## SIGNALS ON THE DISTRIBUTIONS NOTEBOOK ##
    def distribution_apply(self, button):
        self.__distributions[self.__current_distribution] = self.__base_distributions[self.__current_base_distribution].xml(self.__current_distribution)
        self.__widgets.get_widget("distribution_revert").set_sensitive(True)

    def distribution_revert(self, button):
        if self.__distributions[self.__current_distribution] != None:
            base = self.__distributions[self.__current_distribution].find("base").text

            if self.__current_base_distribution != self.__base_distributions_by_name[base].pretty_name:
                self.__base_distributions[self.__current_distribution_algorithm].remove_elements()
                self.__base_distributions_by_name[base].append_elements(self.__widgets.get_widget("distribution_vbox"))

            self.__base_distributions_by_name[base].open_state(self.__distributions[self.__current_distribution])
            self.__current_distribution_algorithm = self.__base_distributions_by_name[base].pretty_name

    def distribution_list_clicked(self, widget, event):
        if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:

            if widget.get_selection().get_selected()[1]:
                name = self.__distribution_list.get_value(widget.get_selection().get_selected()[1], 0)
                self.__current_distribution = name

                self.__widgets.get_widget("distribution_name").set_text("<big><b>%s</b></big>" % (name,))
                self.__widgets.get_widget("distribution_name").set_use_markup(True)
                self.__widgets.get_widget("distribution_vbox").set_sensitive(True)

                self.__widgets.get_widget("distribution_revert").set_sensitive(False)
                self.__widgets.get_widget("distribution_combobox").set_active(-1)
                self.__widgets.get_widget("distribution_apply").set_sensitive(False)

                if self.__distributions[name]:
                    base        = self.__distributions[name].find("base").text
                    pretty_base = self.__base_distributions_by_name[base].pretty_name
                    modelo      = self.__widgets.get_widget("distribution_combobox").get_model()

                    for i in modelo:
                        if i[0] == pretty_base:
                            self.__widgets.get_widget("distribution_combobox").set_active_iter(i.iter)

                    self.__base_distributions[pretty_base].open_state(self.__distributions[name])
                    self.__widgets.get_widget("distribution_apply").set_sensitive(True)
                elif self.__current_base_distribution != "":
                    self.__base_distributions[self.__current_base_distribution].remove_elements()


    def add_distribution(self, widget):
        name = self.__widgets.get_widget("new_distribution_name").get_text().strip()

        if name != "" and name not in self.__distributions.keys():
            self.__distributions[name] = None
            self.__distribution_list.append(None, [name])
            self.__root.add_distribution(name)

        self.__widgets.get_widget("new_distribution_name").set_text("")

    def distribution_combobox_changed(self, combo):
        if self.__current_base_distribution != "":
            self.__base_distributions[self.__current_base_distribution].remove_elements()

        current = combo.get_model()[combo.get_active()][0]

        self.__current_base_distribution = current
        self.__base_distributions[current].append_elements(self.__widgets.get_widget("distribution_vbox"))
        self.__widgets.get_widget("distribution_apply").set_sensitive(True)

    ## SIGNALS ON THE ELEMENTS NOTEBOOK ##
    def add_element_button_click(self, button):
        entry = self.__widgets.get_widget("new_element_name")
        text = entry.get_text().strip()

        if text != '' and text not in self.__myelements:
            self.__myelements.append(text)
            self.__element_list.append(None, [text])
            self.__configured_elements[text] = None
        entry.set_text("")

    def transform_ok(self, button):
        if self.__current_element != self.__conf_root:
            self.__configured_elements[self.__current_element] = self.__transform.xml(self.__current_element, self.__type)
        else:
            self.__configured_elements[self.__current_element] = self.__root.xml(self.__current_element)
        #self.__widgets.get_widget("transform_undo").set_sensitive(True)

    def element_list_click(self, widget, event):
        if event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS and len(self.__element_list) > 0:
            entries = []
            if self.__dtd != None:
                if self.__is_tree_view:
                    entries += [ ("Return to list view", self.return_to_list_view) ]
                elif self.__element_tree.get_selection().get_selected()[1] != None:
                    entries += [ ("Show tree view", self.show_tree_button) ]

            if self.__element_tree.get_selection().get_selected()[1]:
                entries += [ ("Configure as root", self.set_root)]

            menu = gtk.Menu()
            for ident, callback in entries:
                item = gtk.ImageMenuItem(ident)
                item.connect("activate", callback)
                item.show()
                menu.append(item)

            menu.popup(None, None, None, event.button, event.time)
        elif event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
            name = self.__element_list.get_value(self.__element_tree.get_selection().get_selected()[1], 0)

            if name[-3:] == "(*)":
                name = name[:-4]

            if name == self.__current_element:
                return

            self.__widgets.get_widget("element_name").set_text("<big><b>%s</b></big>" % (name,))
            self.__widgets.get_widget("element_name").set_use_markup(True)

            if self.__current_element == "":
                if name == self.__conf_root:
                    self.__root.append_elements(self.__widgets.get_widget("transform_vbox"))
                    widget = self.__root
                else:
                    self.__transform.append_elements(self.__widgets.get_widget("transform_vbox"), self.__type == "xxml")
                    widget = self.__transform
            else:
                if self.__current_element == self.__conf_root:
                    self.__root.remove_elements()
                    self.__transform.append_elements(self.__widgets.get_widget("transform_vbox"), self.__type == "xxml")
                    widget = self.__transform
                elif name == self.__conf_root:
                    self.__transform.remove_elements()
                    self.__root.append_elements(self.__widgets.get_widget("transform_vbox"))
                    widget = self.__root
                else:
                    self.__transform.clean_up()
                    widget = self.__transform

            self.__current_element = name

#            if self.__configured_elements[name] != None:
#                widget.load(self.__configured_elements[name])
            self.__widgets.get_widget("transform_vbox").set_sensitive(True)
            self.__widgets.get_widget("transform_ok").set_sensitive(True)

    def set_root(self, button):
        if self.__element_tree.get_selection().get_selected()[1]:
            self.__conf_root = self.__element_list.get_value(self.__element_tree.get_selection().get_selected()[1], 0)

            if self.__conf_root[-3:] == "(*)":
                self.__conf_root = self.__conf_root[:-4]

            if self.__dtd:
                self.write_dtd_tree(self.__dtd.generate_tree(self.__conf_root))
            else:
                self.return_to_list_view(None)

    def show_tree_button(self, button):
        """Signal for menu press, on the element_list treeview"""
        if self.__element_tree.get_selection().get_selected()[1]:
            tagname =  self.__element_list.get_value(self.__element_tree.get_selection().get_selected()[1], 0)
            self.write_dtd_tree(self.__dtd.generate_tree(tagname))

    def return_to_list_view(self, button):
        """Signal for menu press, on the element_list treeview"""
        self.__element_list.clear()

        if self.__dtd:
            for i in self.__dtd.elements:
                if i == self.__conf_root:
                    self.__element_list.append(None, [i + " (*)"])
                else:
                    self.__element_list.append(None, [i])
        else:
            for i in self.__myelements:
                if i == self.__conf_root:
                    self.__element_list.append(None, [i + " (*)"])
                else:
                    self.__element_list.append(None, [i])
        self.__is_tree_view = False

    def load_dtd_press(self, button):
        """Signal for button press"""
        file_filter = gtk.FileFilter()
        file_filter.add_pattern("*.dtd")

        buttons = (
            gtk.STOCK_OPEN,   gtk.RESPONSE_ACCEPT,
            gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
        )

        file_dialog = gtk.FileChooserDialog("Load DTD", self.__hauptfenster, buttons=buttons)
        file_dialog.set_select_multiple(False)
        file_dialog.set_filter(file_filter)

        if file_dialog.run() == gtk.RESPONSE_ACCEPT:
            fname =  file_dialog.get_filenames()[0]
            self.__dtd = dtd_parser(fname)

            self.__widgets.get_widget("new_element_name").set_sensitive(False)
            self.__widgets.get_widget("add_element_button").set_sensitive(False)

            self.__element_list.clear()
            for x in self.__dtd.elements:
                self.__element_list.append(None, [x])
                self.__configured_elements[x] = None

            self.__is_tree_view = False

        file_dialog.destroy()

    # Stuff that deals with the UI
    def __load_algorithms(self):
        root = et.parse("%s/algorithms/%s.xml" % (self.__bpath, self.__type)).getroot()
        self.__base_algorithms   = dict([(x.pretty_name, x) for x in map(algorithm, root.findall("algorithm"))])
        self.__base_algo_by_name = dict([(x.name, x) for x in self.__base_algorithms.values()])

        if self.__widgets.get_widget("algorithm_combobox").get_model() == None:
            combo_store = gtk.ListStore(str)
            self.__widgets.get_widget("algorithm_combobox").set_model(combo_store)
        else:
            combo_store = self.__widgets.get_widget("algorithm_combobox").get_model()
            combo_store.clear()

        [combo_store.append([x]) for x in self.__base_algorithms]

    # The real stuff
    def write_dtd_tree(self, dic, previous = None):
        if previous == None:
            self.__is_tree_view = True
            self.__element_list.clear()

        for tag, value in dic.items():
            if tag == self.__conf_root:
                tag += " (*)"

            current = self.__element_list.append(previous, [tag])
            for i in value.values():
                self.write_dtd_tree(i, current)
